#include "Camera.h"

Camera::Camera(Config* config) {
	SetLens(XMConvertToRadians(45.0f), (float)config->SCREENRES_HOR / (float)config->SCREENRES_VER, 1.0f, 100.0f);
	this->config = config;
	ResetCamera();
}

void Camera::ResetCamera() {
	mPosition = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
	mRight    = XMVectorSet(1.0f, 0.0f, 0.0f, 0.0f);
	mUp       = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);;
	mLook     = XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f);;
	
	Strafe(XMVectorGetX(config->CameraInitialPosition) - XMVectorGetX(mPosition));
	Hover(XMVectorGetY(config->CameraInitialPosition) - XMVectorGetY(mPosition));
	Walk(XMVectorGetZ(config->CameraInitialPosition) - XMVectorGetZ(mPosition));
}

XMVECTOR Camera::GetPositionXM() const {
	return mPosition;
}

void Camera::SetPosition(XMVECTOR newPosition) {
	mPosition = newPosition;
}

XMVECTOR Camera::GetRight() const {
	return mRight;
}

XMVECTOR Camera::GetUp() const {
	return mUp;
}

XMVECTOR Camera::GetLook() const {
	return mLook;
}

float Camera::GetNearZ() const {
	return mNearZ;
}

float Camera::GetFarZ() const {
	return mFarZ;
}

float Camera::GetAspect() const {
	return mAspect;
}

float Camera::GetFovY() const {
	return mFovY;
}

float Camera::GetFovX() const {
	float halfWidth = 0.5f * GetNearWindowWidth();
	return 2.0f*atan(halfWidth / mNearZ);
}

float Camera::GetNearWindowWidth() const {
	return mAspect * mNearWindowHeight;
}

float Camera::GetNearWindowHeight() const {
	return mNearWindowHeight;
}

float Camera::GetFarWindowWidth() const {
	return mAspect * mFarWindowHeight;
}

float Camera::GetFarWindowHeight() const {
	return mFarWindowHeight;
}

void Camera::SetLens(float fovY, float aspectRatio, float nearViewPlane, float farViewPlane) {
	mFovY   = fovY;
	mAspect = aspectRatio;
	mNearZ  = nearViewPlane;
	mFarZ   = farViewPlane;

	mNearWindowHeight = 2.0f * mNearZ * tanf( 0.5f*mFovY );
	mFarWindowHeight  = 2.0f * mFarZ * tanf( 0.5f*mFovY );

	mProj = XMMatrixPerspectiveFovLH(XMConvertToRadians(45.0f),
									 (FLOAT)config->SCREENRES_HOR / (FLOAT)config->SCREENRES_VER,
									 nearViewPlane,
									 farViewPlane);
}

void Camera::LookAt(XMVECTOR pos, XMVECTOR target, XMVECTOR worldUp) {
	XMVECTOR L = XMVector3Normalize(XMVectorSubtract(target, pos));
	XMVECTOR R = XMVector3Normalize(XMVector3Cross(worldUp, L));
	XMVECTOR U = XMVector3Cross(L, R);

	mPosition = pos;
	mLook = L;
	mRight = R;
	mUp = U;
}

XMMATRIX Camera::View() const {
	return mView;
}

XMMATRIX Camera::Proj() const {
	return mProj;
}

XMMATRIX Camera::ViewProj() const {
	return XMMatrixMultiply(mView, mProj);
}

void Camera::Strafe(float d) {
	XMVECTOR s = XMVectorSet(d, d, d, 0.0f);
	XMVECTOR r = mRight;
	XMVECTOR p = mPosition;

	mPosition = XMVectorSet(XMVectorGetX(s) * XMVectorGetX(r) + XMVectorGetX(p),
							XMVectorGetY(s) * XMVectorGetY(r) + XMVectorGetY(p),
							XMVectorGetZ(s) * XMVectorGetZ(r) + XMVectorGetZ(p),
							0.0f);
}

void Camera::Hover(float d) {
	XMVECTOR s = XMVectorSet(d, d, d, 0.0f);
	XMVECTOR r = mUp;
	XMVECTOR p = mPosition;

	mPosition = XMVectorSet(XMVectorGetX(s) * XMVectorGetX(r) + XMVectorGetX(p),
							XMVectorGetY(s) * XMVectorGetY(r) + XMVectorGetY(p),
							XMVectorGetZ(s) * XMVectorGetZ(r) + XMVectorGetZ(p),
							0.0f);
}

void Camera::Walk(float d) {
	XMVECTOR distance = XMVectorSet(d, d, d, 0.0f);
	XMVECTOR l = mLook;
	XMVECTOR p = mPosition;

	XMVectorSetX(mPosition, XMVectorGetX(distance) * XMVectorGetX(l) + XMVectorGetX(p));
	XMVectorSetZ(mPosition, XMVectorGetZ(distance) * XMVectorGetZ(l) + XMVectorGetZ(p));
	if (config->FlyCamera)
		XMVectorSetY(mPosition, XMVectorGetY(distance) * XMVectorGetY(l) + XMVectorGetY(p));
}

void Camera::Pitch(float angle) {
	XMMATRIX R = XMMatrixRotationAxis(mRight, angle);

	mUp = XMVector3TransformNormal(mUp, R);
	mLook = XMVector3TransformNormal(mLook, R);


	//// Rotate up and look vector about the right vector.
	//XMMATRIX R = XMMatrixRotationAxis(XMLoadFloat3(&mRight), angle);

	//XMStoreFloat3(&mUp,   XMVector3TransformNormal(XMLoadFloat3(&mUp), R));
	//XMStoreFloat3(&mLook, XMVector3TransformNormal(XMLoadFloat3(&mLook), R));
}

void Camera::RotateY(float angle) {
	XMMATRIX R = XMMatrixRotationY(angle);

	mRight = XMVector3TransformNormal(mRight, R);
	mUp = XMVector3TransformNormal(mUp, R);
	mLook = XMVector3TransformNormal(mLook, R);
}

void Camera::UpdateViewMatrix() {
	XMVECTOR R = mRight;
	XMVECTOR U = mUp;
	XMVECTOR L = mLook;
	XMVECTOR P = mPosition;

	L = XMVector3Normalize(L);
	U = XMVector3Normalize(XMVector3Cross(L, R));
	R = XMVector3Cross(U, L);

	// Fill in the view matrix entries.
	float x = -1.0f * XMVectorGetX(XMVector3Dot(P, R)); //XMVector3Dot returns XMVECTOR
	float y = -1.0f * XMVectorGetX(XMVector3Dot(P, U)); //...with result of dot product
	float z = -1.0f * XMVectorGetX(XMVector3Dot(P, L)); //...repeated in all components.

	mRight = R;
	mUp = U;
	mLook = L;

	mView.r[0] = mRight;
	mView.r[1] = mUp;
	mView.r[2] = mLook;
	mView.r[3] = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);
}
